package com.adopapa.ethexplorer.contoller;

import java.math.BigInteger;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.web3j.codegen.JavaFunctionWrapperGenerator;
import org.web3j.crypto.Credentials;
import org.web3j.crypto.ECKeyPair;
import org.web3j.protocol.core.methods.response.TransactionReceipt;

import com.adopapa.ethexplorer.domain.ContractInfo;
import com.adopapa.ethexplorer.domain.ContractInfoService;
import com.adopapa.ethexplorer.service.ChaindataService;
import com.adopapa.ethexplorer.utils.Tools;

import reactor.core.publisher.Mono;

@RestController
@RequestMapping("/api/v1/contract")
public class ContractController extends BaseController {

	@Autowired
	private ChaindataService chaindataService;
	@Autowired
	private ContractInfoService contractInfoService;

	@GetMapping(path = "/contracts/{pageNo}", produces = MediaType.APPLICATION_JSON_VALUE)
	public Mono<Map<String, Object>> getContractInfoPage(@PathVariable Integer pageNo, //
			@RequestParam(required = false) Integer pageSize) {
		pageSize = Tools.empty(pageSize) ? 10 : pageSize;

		Page<ContractInfo> page = contractInfoService.getContractInfoPage(pageNo, pageSize, new ContractInfo());

		return Mono.just(ok(page));

	}

	@PostMapping(path = "/deploy", produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
	public Mono<Map<String, Object>> deployContract(@RequestBody Map<String, Object> params) {
		String abi = getMapValue(params, "abi", String.class);
		String binary = getMapValue(params, "binary", String.class);
		String contractName = getMapValue(params, "contractName", String.class);

		if (Tools.empty(binary) || Tools.empty(abi) || Tools.empty(contractName)) {
			throw new RuntimeException("abi, binary, and contractName can not be empty");
		}
		String privateKey = "00be6b6223868e4ca31bf986fac76e9190fdcfa78bc432647e51cb43599628def7";
		ECKeyPair ecKeyPair = ECKeyPair.create(new BigInteger(privateKey, 16));
		Credentials credentials = Credentials.create(ecKeyPair);
		System.out.println(credentials.getAddress());

		try {
//			String accountAddress = "0x25e63ae307a51c70493d29ddf145c2d7113fa943";
			String txHash = chaindataService.deployContract(credentials, binary);

			ContractInfo contractInfo = new ContractInfo().abi(abi).binary(binary)//
					.contractName(contractName).txHash(txHash).createTime(Instant.now());

			TransactionReceipt receipt = chaindataService.getTransactionReceipt(txHash);
			if (!Tools.empty(receipt)) {
				contractInfo.address(receipt.getContractAddress());
			}
			contractInfoService.saveContractInfo(contractInfo);

			Map<String, String> result = new HashMap<String, String>();
			result.put("txHash", txHash);
			if (!Tools.empty(receipt)) {
				result.put("address", receipt.getContractAddress());
			}

			return Mono.just(ok(result));

		} catch (Exception e) {
			throw new RuntimeException(e.getMessage());
		}

	}

	@GetMapping(path = "/hash/{txHash}", produces = MediaType.APPLICATION_JSON_VALUE)
	public Mono<Map<String, Object>> getDeployResult(@PathVariable String txHash) {
		ContractInfo contractInfo = contractInfoService.getContractInfoByHash(txHash);
		if (Tools.empty(contractInfo)) {
			throw new RuntimeException("No this contract");
		}

		Map<String, String> result = new HashMap<String, String>();
		result.put("txHash", txHash);

		if (!Tools.empty(contractInfo.getAddress())) {
			result.put("address", contractInfo.getAddress());
			return Mono.just(ok(result));
		}

		TransactionReceipt receipt = chaindataService.getTransactionReceipt(txHash);
		if (Tools.empty(receipt)) {
			return Mono.just(other("TX pending"));
		}

		contractInfo.address(receipt.getContractAddress()).valid(Boolean.TRUE);
		contractInfoService.saveContractInfo(contractInfo);

		result.put("address", contractInfo.getAddress());
		return Mono.just(ok(result));

	}

	@PostMapping(path = "/javacode", produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
	public Mono<Map<String, Object>> generateJavaCode(@RequestBody Map<String, Object> params) {
		String abi = getMapValue(params, "abi", String.class);
		String binary = getMapValue(params, "binary", String.class);
		String contractName = getMapValue(params, "contractName", String.class);
		String packageName = getMapValue(params, "packageName", String.class);

		if (Tools.empty(abi) //
				|| Tools.empty(binary) //
				|| Tools.empty(contractName) //
				|| Tools.empty(packageName)) {
			throw new RuntimeException("abi, binary, contractName, and packageName can not be empty");
		}
		try {
			String javacode = new JavaFunctionWrapperGenerator(packageName, contractName, binary, abi).generate();
			return Mono.just(ok(javacode));
		} catch (Exception e) {
			throw new RuntimeException(e.getMessage());
		}

	}
	
}
